package ch.exm.storm.functional;

import static ch.exm.storm.functional.matcher.IsNull.notNullValue;

import java.lang.reflect.Array;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import ch.exm.storm.collections.PropertyComparator;
import ch.exm.storm.functional.functions.Aggregator;
import ch.exm.storm.functional.functions.Convertor;
import ch.exm.storm.functional.functions.MaxFinder;
import ch.exm.storm.functional.functions.MinFinder;
import ch.exm.storm.functional.functions.PropertyExtractor;
import ch.exm.storm.functional.functions.Summer;
import ch.exm.storm.functional.matcher.Matcher;

public class CollectionUtil {

	public static <T> T from(T t) {
		if (t == null) return null;
		return from(Arrays.asList(t));
	}

	public static <T> T from(Collection<T> c) {
		return (c == null || c.isEmpty()) ? null : from(c, c.iterator().next().getClass());
	}

	public static <T> T from(Collection<T> c, Class<?> t) {
		return (T) Proxy.newProxyInstance(t.getClassLoader(), getIfaces(t), new ProxyIterator<T>(c));
	}

	private static Class<?>[] getIfaces(Class<?> t) {
		Class<?>[] implemented = t.isInterface() ? new Class<?>[] { t } : t.getInterfaces();
		Class<?>[] ifaces = new Class[implemented.length + 1];
		for (int i = 0; i < implemented.length; i++) ifaces[i] = implemented[i];
		ifaces[implemented.length] = Iterable.class;
		return ifaces;
	}

	public static <T> T[] toArray(Collection<T> c) {
		if (c == null || c.isEmpty()) return null;
		return toArray(c, c.iterator().next().getClass());
	}

	public static <T> T[] toArray(Collection<T> c, Class<?> t) {
		return c.toArray((T[]) Array.newInstance(t, c == null ? 0 : c.size()));
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// Collection
	// ////////////////////////////////////////////////////////////////////////

	public static <T> Collection<? extends T> collect(Object iterable) {
		return (Collection<? extends T>) collect((Iterable<?>) iterable);
	}

	public static <T> Collection<? extends T> collect(Iterable<?> iterable) {
		Collection<T> collected = new ArrayList<T>();
		for (Object item : iterable) {
			if (item instanceof Iterable) collected.addAll((Collection<T>) collect(item));
			else collected.add((T) item);
		}
		return collected;
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// Selection
	// ////////////////////////////////////////////////////////////////////////
	
	public static <T> Collection<T> select(Object iterable) {
		return select(iterable, notNullValue());
	}

	public static <T> Collection<T> select(Object iterable, Matcher matcher) {
		return select((Iterable<T>) iterable, matcher);
	}

	public static <T> Collection<T> select(Iterable<T> iterable, Matcher matcher) {
		Collection<T> collected = new ArrayList<T>();
		if(iterable == null) return collected;
		for (T item : iterable) {
			if (matcher.matches(item)) collected.add(item);
		}
		return collected;
	}

	public static <T> T selectUnique(Object iterable, Matcher matcher) {
		return selectUnique((Iterable<T>) iterable, matcher);
	}

	public static <T> T selectUnique(Iterable<T> iterable, Matcher matcher) {
		Collection<T> collected = select(iterable, matcher);
		if (collected.isEmpty()) return null;
		if (collected.size() > 1) throw new RuntimeException("Not unique item. Found: " + collected);
		return collected.iterator().next();
	}

	public static <T> Collection<T> selectDistinct(Object iterable) {
		return selectDistinct((Iterable<T>) iterable, (Comparator<T>) null);
	}

	public static <T> Collection<T> selectDistinct(Iterable<T> iterable) {
		return selectDistinct(iterable, (Comparator<T>) null);
	}

	public static <T> Collection<T> selectDistinct(Object iterable, String propertyName) {
		return selectDistinct((Iterable<T>) iterable, new PropertyComparator<T>(propertyName));
	}

	public static <T> Collection<T> selectDistinct(Iterable<T> iterable, String propertyName) {
		return selectDistinct(iterable, new PropertyComparator<T>(propertyName));
	}

	public static <T> Collection<T> selectDistinct(Object iterable, Comparator<T> comparator) {
		return selectDistinct((Iterable<T>) iterable, comparator);
	}

	public static <T> Collection<T> selectDistinct(Iterable<T> iterable, Comparator<T> comparator) {
		Set<T> collected = comparator == null ? new HashSet<T>() : new TreeSet<T>(comparator);
		if (iterable != null) for (T item : iterable)
			collected.add(item);
		return collected;
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// Aggregation
	// ////////////////////////////////////////////////////////////////////////

	private static final Summer SUMMER = new Summer();
	private static final MinFinder MIN_FINDER = new MinFinder();
	private static final MaxFinder MAX_FINDER = new MaxFinder();

	public static <T> T aggregate(Object iterable, Aggregator<T> aggregator) {
		return aggregate((Iterable<T>) iterable, aggregator);
	}

	public static <T> T aggregate(Iterable<T> iterable, Aggregator<T> aggregator) {
		T result = aggregator.emptyItem();
		if (iterable != null) for (T item : iterable)
			result = aggregator.aggregate(result, item);
		return result;
	}

	public static <T, A> T aggregateFrom(Collection<T> c, Aggregator<A> a) {
		return (c == null || c.isEmpty()) ? null : aggregateFrom(c, c.iterator().next().getClass(), a);
	}

	public static <T, A> T aggregateFrom(Collection<T> c, Class<?> t, Aggregator<A> a) {
		return (T) Proxy.newProxyInstance(t.getClassLoader(), getIfaces(t), new ProxyAggregator<T, A>(c, a));
	}

	// -- (Sum) ---------------------------------------------------------------

	public static Number sum(Object iterable) {
		return sum((Iterable<Number>) iterable);
	}

	public static Number sum(Iterable<Number> iterable) {
		return aggregate(iterable, SUMMER);
	}

	public static <T> T sumFrom(Collection<T> c) {
		return aggregateFrom(c, SUMMER);
	}

	public static <T> T sumFrom(Collection<T> c, Class<?> t) {
		return aggregateFrom(c, t, SUMMER);
	}

	// -- (Min) ---------------------------------------------------------------

	public static <T> T min(Object iterable) {
		return min((Iterable<T>) iterable);
	}

	public static <T> T min(Iterable<T> iterable) {
		return (T) aggregate(iterable, MIN_FINDER);
	}

	public static <T> T minFrom(Collection<T> c) {
		return (T) aggregateFrom(c, MIN_FINDER);
	}

	public static <T> T minFrom(Collection<T> c, Class<?> t) {
		return (T) aggregateFrom(c, t, MIN_FINDER);
	}

	// -- (Max) ---------------------------------------------------------------

	public static <T> T max(Object iterable) {
		return max((Iterable<T>) iterable);
	}

	public static <T> T max(Iterable<T> iterable) {
		return (T) aggregate(iterable, MAX_FINDER);
	}

	public static <T> T maxFrom(Collection<T> c) {
		return (T) aggregateFrom(c, MAX_FINDER);
	}

	public static <T> T maxFrom(Collection<T> c, Class<?> t) {
		return (T) aggregateFrom(c, t, MAX_FINDER);
	}

	// ////////////////////////////////////////////////////////////////////////
	// /// Conversion
	// ////////////////////////////////////////////////////////////////////////

	public static <F, T> Collection<T> convert(Object iterable, Convertor<F, T> convertor) {
		return convert((Iterable<T>) iterable, convertor);
	}

	public static <F, T> Collection<T> convert(Iterable<F> iterable, Convertor<F, T> convertor) {
		Collection<T> collected = new ArrayList<T>();
		if (iterable != null) for (F item : iterable) collected.add(convertor.convert(item));
		return collected;
	}

	public static <F, T> Collection<T> extract(Object iterable, String propertyName) {
		return extract((Iterable<T>) iterable, propertyName);
	}

	public static <F, T> Collection<T> extract(Iterable<F> iterable, String propertyName) {
		return convert(iterable, new PropertyExtractor<F, T>(propertyName));
	}
}
